﻿using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.WindowsAPICodePack.Taskbar;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

namespace Batch_Disc_Backup
{
    /// <summary>
    /// Main entry class for Batch Disc Backup.
    /// </summary>
    public partial class FrmMain : Form
    {
        //Indexes for the ListView columns. I'm sure there's a better way to do this.
        private static readonly int COL_INDEX_FROM = 0;
        private static readonly int COL_INDEX_TO = 1;
        private static readonly int COL_INDEX_STATUS = 2;
        private static readonly int COL_INDEX_FILE_NO = 3;
        private static readonly int COL_INDEX_FILE_PROGRESS = 4;
        private static readonly int COL_INDEX_TOTAL_PROGRESS = 5;
        private static readonly int COL_INDEX_SPEED = 6;
        private static readonly int COL_INDEX_ETA = 7;

        //All of the currently running processes are stored here
        private List<DiscCopier> processes = new List<DiscCopier>();

        public FrmMain()
        {
            InitializeComponent();
            SetUpDriveList();
            LoadSettings();
        }

        /// <summary>
        /// Sets up the drive selection combobox
        /// </summary>
        private void SetUpDriveList()
        {
            cboFrom.Items.Clear();
            DriveInfo[] infos = DriveInfo.GetDrives();
            foreach (DriveInfo info in infos)
            {
                if (info.DriveType == DriveType.CDRom)
                {
                    cboFrom.Items.Add(new ListItem(info.RootDirectory.FullName, Quick.SubString(info.RootDirectory.FullName, @":\")));
                }
            }
            if (cboFrom.Items.Count > 0)
                cboFrom.SelectedIndex = 0;
        }

        /// <summary>
        /// Start the DiscCopy process and add to the processes list. These run in seperate threads obviously.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = false;
            Application.DoEvents();

            try
            {
                string fromDl = ListItem.GetSelectedValue(cboFrom);
                if (fromDl == null) throw new Exception("No drive letter selected");
                string to = txtTo.Text.Trim();
                if (to == "") throw new Exception("Please enter in a destination");
                if (!Directory.Exists(to))
                {
                    if (MessageBox.Show(this, "The directory \"" + to + "\" does not exist. Create it?", "Create directory?", MessageBoxButtons.OKCancel, MessageBoxIcon.None) != DialogResult.OK) return;
                }
                foreach (DiscCopier dc in processes)
                {
                    if (dc.DriveLetter.Equals(fromDl, StringComparison.OrdinalIgnoreCase))
                    {
                        MessageBox.Show(this, "There is already a copy process for drive " + dc.DriveLetter + ".", "Disc already copying", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        return;
                    }
                }
                DiscCopier copier = new DiscCopier(fromDl, to, OnProcessUpdate, OnProcessEnded);
                processes.Add(copier);
                ListViewItem lvi = new ListViewItem(new string[lvMain.Columns.Count]);
                UpdateLvi(lvi, copier);
                lvMain.Items.Add(lvi);
                copier.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "An error occured:\r\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                btnStart.Enabled = true;
            }
        }

        /// <summary>
        /// Sets the subitem values for the parsed ListViewItem, which will or has been inserted into the lvMain ListView
        /// </summary>
        /// <param name="lvi"></param>
        /// <param name="process"></param>
        private void UpdateLvi(ListViewItem lvi, DiscCopier process)
        {
            lvMain.BeginUpdate();
            try
            {
                lvi.SubItems[COL_INDEX_FROM].Text = process.RootFrom;
                lvi.SubItems[COL_INDEX_TO].Text = process.RootTo;
                lvi.SubItems[COL_INDEX_STATUS].Text = process.Status;
                if (!process.IsProcessing)
                {
                    lvi.SubItems[COL_INDEX_FILE_NO].Text = "--";
                    lvi.SubItems[COL_INDEX_FILE_PROGRESS].Text = "--";
                    lvi.SubItems[COL_INDEX_TOTAL_PROGRESS].Text = "--";
                    lvi.SubItems[COL_INDEX_SPEED].Text = "--";
                    lvi.SubItems[COL_INDEX_ETA].Text = "--";
                }
                else
                {
                    lvi.SubItems[COL_INDEX_FILE_NO].Text = string.Format("{0} of {1}", process.CurrentFileNo, process.DiscCopyInfo.FileCount);
                    lvi.SubItems[COL_INDEX_FILE_PROGRESS].Text = string.Format("{0}% of {1}", process.CurrentFileLength == 0 ? 0 : process.CurrentBytesCopied * 100 / process.CurrentFileLength, Quick.GetByteString(process.CurrentFileLength));
                    lvi.SubItems[COL_INDEX_TOTAL_PROGRESS].Text = string.Format("{0}% of {1}", process.DiscCopyInfo.TotalFilesLength == 0 ? 0 : process.DiscCopyInfo.TotalBytesCopied * 100 / process.DiscCopyInfo.TotalFilesLength, Quick.GetByteString(process.DiscCopyInfo.TotalFilesLength));
                    double bytesPerSecond = process.TotalBytesPerSecond;
                    lvi.SubItems[COL_INDEX_SPEED].Text = string.Format("{0}/s", Quick.GetByteString((long)bytesPerSecond, 1));
                    if (bytesPerSecond == 0)
                        lvi.SubItems[COL_INDEX_ETA].Text = "--";
                    else {
                        lvi.SubItems[COL_INDEX_ETA].Text = Quick.FriendlyTime(new TimeSpan((long)((double)(process.DiscCopyInfo.TotalFilesLength - process.DiscCopyInfo.TotalBytesCopied) / bytesPerSecond) * 10000000));
                    }
                }
            }
            catch (Exception ex)
            {
                string i = ex.Message;
            }
            finally
            {
                lvMain.EndUpdate();
            }

            switch (process.StatusType)
            {
                case(StatusType.Done):
                    lvi.ForeColor = Color.Gray;
                    break;
                case (StatusType.Error):
                    lvi.ForeColor = Color.Red;
                    break;
                case (StatusType.WaitingForDisc):
                    lvi.ForeColor = Color.Orange;
                    break;
                default:
                    lvi.ForeColor = Color.Black;
                    break;
            }
        }
        /// <summary>
        /// Gets the ListViewItem from lvMain. The identifier is the 'From' column.
        /// </summary>
        /// <param name="process"></param>
        /// <returns></returns>
        private ListViewItem GetLvi(DiscCopier process)
        {
            foreach (ListViewItem item in lvMain.Items)
            {
                if (item.SubItems[0].Text.Equals(process.RootFrom, StringComparison.OrdinalIgnoreCase))
                    return item;
            }
            return null;
        }
        /// <summary>
        /// Gets the DiscCopier process relative to the ListViewItem object
        /// </summary>
        /// <param name="lvi"></param>
        /// <returns></returns>
        private DiscCopier GetDiscCopier(ListViewItem lvi)
        {
            if (lvi == null) return null;
            string fromRoot = lvi.SubItems[0].Text;
            foreach (DiscCopier dc in processes)
            {
                if (dc.RootFrom.Equals(fromRoot, StringComparison.OrdinalIgnoreCase))
                    return dc;
            }
            return null;
        }

        //Flag so that any gui update processes can cancel if required
        private bool isClosing = false;
        /// <summary>
        /// Check if there are any processes that are running but also actually copying files.
        /// If there are any, warn with a confirm box. If all good, stop all of the DiscCopier processes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            int copyingCount = 0;
            foreach (DiscCopier dc in processes)
            {
                if (dc.IsProcessing)
                {
                    copyingCount++;
                }
            }
            if (copyingCount > 0
                && MessageBox.Show(this,
                    "There " + (copyingCount == 1 ? "is one process" : " are " + copyingCount + " processes") + " still copying. Are you sure that you'd like to exit?",
                    "Confirm exit",
                    MessageBoxButtons.OKCancel,
                    MessageBoxIcon.Exclamation) != DialogResult.OK)
            {
                e.Cancel = true;
                return;
            }

            isClosing = true;
            foreach (DiscCopier dc in processes)
            {
                dc.Stop();
            }
        }

        private void txtTo_TextChanged(object sender, EventArgs e)
        {
            SaveSettings();
        }

        private void cboFrom_SelectedIndexChanged(object sender, EventArgs e)
        {
            SaveSettings();
        }

        private Object saveLock = new Object();
        private bool isLoading = false;
        /// <summary>
        /// Load the settings from last time. Called from the constructor.
        /// </summary>
        private void LoadSettings()
        {
            isLoading = true;
            lock (saveLock)
            {
                ListItem.SetSelectedValue(cboFrom, Properties.Settings.Default.FromDrive);
                txtTo.Text = Properties.Settings.Default.ToDir;
            }
            isLoading = false;
        }
        /// <summary>
        /// Saves the settings. Called imediately when the from and to fields are changed.
        /// </summary>
        private void SaveSettings()
        {
            if (isLoading) return; //We don't want to save if we're in the middle of a load
            lock (saveLock)
            {
                Properties.Settings.Default.FromDrive = ListItem.GetSelectedValue(cboFrom);
                Properties.Settings.Default.ToDir = txtTo.Text;
                Properties.Settings.Default.Save();
            }
        }

        /// <summary>
        /// Hide the app from the windows taskbar, and show as a system tray icon instead.
        /// On the first minimise, just notify the user so they know where the application has
        /// gone.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FrmMain_Resize(object sender, EventArgs e)
        {
            OperatingSystem os = Environment.OSVersion;
            if (FormWindowState.Minimized == WindowState)
                //&& !(os.Platform == PlatformID.Win32NT && os.Version.Major >= 7)) //Windows 7 standard to not ever hide from task tray (I think)
            {
                Hide();
                if (!Properties.Settings.Default.FirstTimeMinimise)
                {
                    notifyIcon.ShowBalloonTip(15000, "I've minimised down here!", "(This message won't show again)", ToolTipIcon.Info);
                    Properties.Settings.Default.FirstTimeMinimise = true;
                    Properties.Settings.Default.Save();
                }
            }
        }
        /// <summary>
        /// Show the application again if double click on the system tray icon
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void notifyIcon_DoubleClick(object sender, EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
        }
        /// <summary>
        /// Menu: Right-click notify icon, Open
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
        }

        /// <summary>
        /// Open the from directory in Windows Explorer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lblFrom_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            OpenFolder(cboFrom.Text);
        }

        /// <summary>
        /// Open the disc directory from Windows Explorer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lblTo_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            OpenFolder(txtTo.Text);
        }

        private void OpenFolder(string location)
        {
            if (location.Trim() != "")
            {
                try
                {
                    Process.Start(location.Trim());
                }
                catch { }
            }
        }

        /// <summary>
        /// Open the floder browser dialog.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            try
            {
                folderBrowserDialog.SelectedPath = txtTo.Text;
            }
            catch (Exception ex) { }
            if (folderBrowserDialog.ShowDialog(this) == DialogResult.OK)
            {
                txtTo.Text = folderBrowserDialog.SelectedPath;
            }
        }

        /// <summary>
        /// Callback from the DiscCopier thread for progress updates.
        /// Updates the lvMain ListView.
        /// </summary>
        /// <param name="process"></param>
        /// <param name="ex"></param>
        public void OnProcessUpdate(DiscCopier process, Exception ex)
        {
            if (isClosing) return;
            try
            {
                Invoke(new MethodInvoker(delegate()
                {
                    try
                    {
                        ListViewItem lvi = GetLvi(process);
                        UpdateLvi(lvi, process);
                        UpdateTotalProgress();

                        //Show the error once the disc has finished copying for that disc
                        if (process.StatusType == StatusType.WaitingForDisc && process.LastErrorCount > 0)
                        {
                            string errorMessage = (process.LastErrorCount == 1 ? "There was an error " : "There were some errors ") + "during the copy. Be sure to check the log for details.";
                            notifyIcon.ShowBalloonTip(15000, "An error occured", errorMessage, ToolTipIcon.Error);
                            lblError.Text = errorMessage;
                            panError.Visible = true;
                            process.LastErrorCount = 0; //reset the error count
                        }
                    }
                    catch { }
                }));
            }
            catch { }
        }

        /// <summary>
        /// Called once a process has ended. This is not to be confused with a disc copy finishing.
        /// A complete process only happens when the user right clicks the listviewitem and clicks
        /// 'Stop', or if they close the application.
        /// </summary>
        /// <param name="process"></param>
        public void OnProcessEnded(DiscCopier process)
        {
            if (isClosing) return;
            try
            {
                Invoke(new MethodInvoker(delegate()
                {
                    try
                    {
                        lvMain.Items.Remove(GetLvi(process));
                        processes.Remove(process);
                        UpdateTotalProgress();
                    }
                    catch { }
                }));
            }
            catch { }
        }

        /// <summary>
        /// Updates the taskbar process percentage, and sets the notifyicon text to show how many processes are running.
        /// </summary>
        private void UpdateTotalProgress()
        {
            //Update the taskbar progress
            long totalBytesCopied = 0;
            long totalFilesLength = 0;
            int processingCount = 0;
            foreach (DiscCopier copier in processes)
            {
                totalBytesCopied += copier.DiscCopyInfo.TotalBytesCopied;
                totalFilesLength += copier.DiscCopyInfo.TotalFilesLength;
                processingCount++;
            }

            if (totalFilesLength == 0)
                TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);
            else
            {
                TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal);
                TaskbarManager.Instance.SetProgressValue((int)(1000 * totalBytesCopied / totalFilesLength), 1000);
            }
            
            //Update the notification icon text
            /*StringBuilder sb = new StringBuilder();
            sb.Append("Batch Disc Backup");
            if (processes.Count > 0)
            {
                sb.Append("Processing: ");
            }
            foreach (DiscCopier copier in processes)
            {
                sb.Append("\r\n - ");
                sb.Append(copier.RootFrom);

                if (copier.VolumeLabel != null)
                {
                    sb.Append(" (");
                    sb.Append(copier.VolumeLabel);
                    sb.Append(")");
                }
                if (copier.IsProcessing && copier.TotalFilesLength != 0)
                {
                    sb.Append(" ");
                    sb.Append(copier.TotalBytesCopied * 100 / copier.TotalFilesLength);
                    sb.Append("% of ");
                    sb.Append(Quick.GetByteString(copier.TotalFilesLength));
                    sb.Append(" complete");
                }
                else
                {
                    sb.Append(copier.Status);
                }
            }
            notifyIcon.Text = sb.ToString();*/
            //Ffff...maximum length of 64 characters allowed

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Batch Disc Backup");
            if (processingCount > 0)
            {
                if (processingCount > 1)
                    sb.AppendLine(string.Format("{0} discs copying", processingCount));
                sb.AppendLine(string.Format("{0}% of {1} complete", totalBytesCopied == 0 ? 0 : (totalBytesCopied * 100 / totalFilesLength), Quick.GetByteString(totalFilesLength)));
            }
            notifyIcon.Text = sb.ToString();
        }

        /// <summary>
        /// Cancel the right click on the lvMenu if no items are selected
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
        {
            if (lvMain.SelectedItems.Count <= 0)
                e.Cancel = true;
        }

        /// <summary>
        /// Right-click and Stop was clicked on a process item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void stopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            int processingCount = 0;
            foreach (ListViewItem lvi in lvMain.SelectedItems)
            {
                DiscCopier dc = GetDiscCopier(lvi);
                if (dc.IsProcessing) processingCount++;
            }
            if (processingCount == 0
                || MessageBox.Show(this,
                    processingCount + " processe" + (processingCount == 1 ? "" : "s")
                        + " are currently copying.\r\nAre you sure that you'd like to cancel "
                        + (processingCount == 1 ? "it?" : "them?"),
                    "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.Yes)
            {
                foreach (ListViewItem lvi in lvMain.SelectedItems) {
                    DiscCopier dc = GetDiscCopier(lvi);
                    if (dc != null) dc.Stop();
                }
            }
        }

        /// <summary>
        /// Menu: File, Close
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        /// <summary>
        /// Show the readme webpage
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void viewHelpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string helpFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"readme\index.html");
            if (File.Exists(helpFile))
            {
                Process.Start(helpFile);
            }
            else
            {
                MessageBox.Show(this, String.Format("Help file not found:\r\n{0}", helpFile), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private FrmAbout frmAbout = null;
        /// <summary>
        /// Show About form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (frmAbout == null) frmAbout = new FrmAbout();
            frmAbout.ShowDialog(this);
        }

        private FrmLog frmLog = new FrmLog();
        private void showLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowLog();
        }
        /// <summary>
        /// Shows the log form
        /// </summary>
        private void ShowLog()
        {
            panError.Visible = false;
            frmLog.ShowDialog(this);
        }
        /// <summary>
        /// Hide the error message
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lblErrorHide_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            panError.Visible = false;
        }
        private void lblErrorShowLog_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            ShowLog();
        }

        

        /// <summary>
        /// Refresh the cboFrom combobox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRefreshDrives_Click(object sender, EventArgs e)
        {
            string selectedVal = ListItem.GetSelectedValue(cboFrom);
            SetUpDriveList();
            ListItem.SetSelectedValue(cboFrom, selectedVal);
        }
    }
}
